#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <string.h>
#include <stdint.h>

#include "dx6i.h"
#include "rxtx.h"
#include "conf.h"

extern cnt_t cnt;
volatile conf_t conf;
volatile uint8_t conf_item_idx;

const char conf_item_0[] PROGMEM = "Transmitter beeps";
const char conf_item_1[] PROGMEM = "Lights on beep";
const char conf_item_2[] PROGMEM = "Vibrate on beep";
const char conf_item_3[] PROGMEM = "Beeps on inactivity";
const char conf_item_4[] PROGMEM = "Lights on inactivity";
const char conf_item_5[] PROGMEM = "Vibrate on inactivity";
const char conf_item_6[] PROGMEM = "Lights off dimming";
const char conf_item_7[] PROGMEM = "Inactivity alarm after (m)";
const char conf_item_8[] PROGMEM = "Inactivity alarm repeat (s)";
const char conf_item_9[] PROGMEM = "Lights off after (s)";
const char conf_item_a[] PROGMEM = "Vibrate beep time (ms)";
const char conf_item_b[] PROGMEM = "Vibrate beep detect (ms)";
const char conf_item_c[] PROGMEM = "Vibrate alarm time (ms)";
const char conf_item_d[] PROGMEM = "Lights power (%)";
const char conf_item_e[] PROGMEM = "Vibro motor power (%)";

const char *conf_items[] PROGMEM = {
	conf_item_0,
	conf_item_1,
	conf_item_2,
	conf_item_3,
	conf_item_4,
	conf_item_5,
	conf_item_6,
	conf_item_7,
	conf_item_8,
	conf_item_9,
	conf_item_a,
	conf_item_b,
	conf_item_c,
	conf_item_d,
	conf_item_e
};

const conf_item_adj_t conf_items_adj[] = {
	{CNT_MIN, CNT_MIN, CNT_MIN * 10},
	{CNT_SEC, CNT_SEC * 2, CNT_SEC * 120},
	{CNT_SEC, CNT_SEC * 10, CNT_SEC * 180},
	{CNT_MSEC, CNT_MSEC, CNT_MSEC * 200},
	{CNT_MSEC, CNT_MSEC * 10, CNT_SEC * 2},
	{CNT_MSEC, CNT_MSEC * 100, CNT_SEC * 2},
	{PWM_LCD_CNT/100, PWM_LCD_CNT/10, PWM_LCD_CNT},
	{PWM_VIBRO_CNT/100, PWM_VIBRO_CNT/10, PWM_VIBRO_CNT}
};

void conf_loop(void){
	cnt.conf = CNT_CONF;

	while(cnt.conf){
		if(ch_is_min(CH_YAW) && ch_is_min(CH_THR) && ch_is_mid(CH_ROLL) && ch_is_mid(CH_PITCH)){
			conf.f&= ~(1 << CB_BEEP_BEEP);
			break;

		}else if(ch_is_min(CH_YAW) && ch_is_mid(CH_THR) && ch_is_mid(CH_ROLL) && ch_is_mid(CH_PITCH)){
			rxtx_sync_mode(true);
			tx_push_str_P(PSTR("Please set YAW back to center position to confirm"));

			DEBUG_LED_ON
			while(cnt.conf && (!ch_is_mid(CH_YAW) || !ch_is_mid(CH_THR) || !ch_is_mid(CH_ROLL) || !ch_is_mid(CH_PITCH)));
			DEBUG_LED_OFF

			if(cnt.conf){
				tx_push_str_P(PSTR("\rDumping current settings:                         \r\n"));
				for(conf_item_idx = 0; conf_item_idx < SIZE_OF(conf_items); conf_item_idx++){
					tx_push_char('\n');
					push_conf_item(CA_SHOW);
				}
				tx_push_str_P(PSTR("\r\n\nEnter Configuration\r\n\r\nPITCH      [select]\r\nROLL       [change]\r\nYAW-LEFT   [discard & exit]\r\nYAW-RIGHT  [save & exit]\r\n\r\n"));
				conf_item_idx = 0;
				push_conf_item(CA_SHOW);
				while(push_conf_item(conf_action()));
			}else{
				tx_push_str_P(PSTR("\r\nTime out ("));
				tx_push_digit(CNT_CONF / CNT_SEC);
				tx_push_str_P(PSTR(" sec)\r\n"));
			}

			rxtx_sync_mode(false);
		}
	}

	tx_push_str_P(PSTR("dx6i version " VERSION "\r\n"));
	TX_SEND

	while(true)
		dump_channels();
}

static conf_action_t conf_action(void){
	if(ch_is_max(CH_PITCH) && conf_action_confirm(CH_PITCH))
		return CA_PREV;

	if(ch_is_min(CH_PITCH) && conf_action_confirm(CH_PITCH))
		return CA_NEXT;

	if(ch_is_max(CH_ROLL) && conf_action_confirm(CH_ROLL))
		return CA_INC;

	if(ch_is_min(CH_ROLL) && conf_action_confirm(CH_ROLL))
		return CA_DEC;

	if(ch_is_max(CH_YAW) && conf_action_confirm(CH_YAW))
		return CA_SAVE_EXIT;

	if(ch_is_min(CH_YAW) && conf_action_confirm(CH_YAW))
		return CA_DISCARD_EXIT;

	return CA_NONE;
}

static bool conf_action_confirm(uint8_t i){
	DEBUG_LED_ON
	while(!ch_is_mid(i));
	DEBUG_LED_OFF
	return true;
}

static bool push_conf_item(conf_action_t action){
	switch(action){
		case CA_NEXT:
			if(++conf_item_idx >= SIZE_OF(conf_items))
				conf_item_idx = SIZE_OF(conf_items) - 1;
			break;
		case CA_PREV:
			if(conf_item_idx)
				conf_item_idx--;
			break;
		case CA_SAVE_EXIT:
			tx_push_str_P(PSTR("\r\nExit & Save\r\n"));
			write_conf();
			return false;
		case CA_DISCARD_EXIT:
			tx_push_str_P(PSTR("\r\nExit & Discard\r\n"));
			read_conf();
			return false;
		case CA_NONE:
			return true;
	}

	tx_push_char('\r');
	tx_push_str_P((const char PROGMEM*)pgm_read_word(conf_items + conf_item_idx));
	tx_push_str_P(PSTR(": "));

	if(conf_item_idx < CONF_BITS_SIZE){
		if(action == CA_INC)
			conf.f|= (1 << conf_item_idx);
		else if(action == CA_DEC)
			conf.f&= ~(1 << conf_item_idx);

		tx_push_str_P((conf.f & (1 << conf_item_idx)) ? PSTR("Yes") : PSTR("No"));

	}else{
		const conf_item_adj_t *conf_item_adj = &conf_items_adj[conf_item_idx - CONF_BITS_SIZE];
		uint32_t *val = &((uint32_t*)&conf)[conf_item_idx - CONF_BITS_SIZE + 1];

		if(action == CA_INC){
			if(*val < conf_item_adj->max)
				(*val)+= conf_item_adj->step;
			else
				*val = conf_item_adj->max;
		}else if(action == CA_DEC){
			if(*val >= conf_item_adj->step)
				(*val)-= conf_item_adj->step;
			else
				*val = 0;
		}

		if(*val)
			tx_push_digit(*val / conf_item_adj->div);
		else
			tx_push_str_P(PSTR("Off"));
	}

	tx_push_str_P(PSTR("             "));

	if(action == CA_SHOW)
		return true;

	switch(conf_item_idx){
		case 0: // "Transmitter beeps";
			beep_on(conf.f & (1 << CB_BEEP_BEEP) ? 2 : 1);
			break;
		case 3: // "Beeps on inactivity";
			beep_on(conf.f & (1 << CB_AFT_BEEP) ? 3 : 1);
			break;
		case 1: // "Lights on beep";
		case 4: // "Lights on inactivity";
		case 6: // "Lights off dimming";
		case 13: // "Lights power (%)";
			cnt.lcd = CNT_SEC;
			cnt.pwm_lcd = PWM_LCD_CNT;
			cnt.pwm_lcd_val = conf.lcd_power;
			break;
		case 9: // "Lights off after (s)";
			lcd_on();
			break;
		case 5: // "Vibrate on inactivity";
			if(conf.f & (1 << CB_AFT_VIBRO))
				vibro_on(false);
			break;
		case 12: // "Vibrate detect (ms)";
		case 14: // "Vibro motor power (%)";
			vibro_on(false);
			break;
	}

	return true;
}

void read_conf(void){
	eeprom_read_block((void*)&conf, (void*)0, sizeof(conf));
	if(conf.crc8 != crc8((uint8_t*)&conf, sizeof(conf) - 1)){
		conf.f = (1 << CB_BEEP_BEEP) | (1 << CB_BEEP_LCD) | (1 << CB_BEEP_VIBRO) | (1 << CB_AFT_BEEP) | (1 << CB_AFT_LCD) | (1 << CB_AFT_VIBRO) | (1 << CB_LCD_DIMM);
		conf.aft = CNT_MIN * 2;
		conf.aft_repeat = CNT_SEC * 6;
		conf.lcd = CNT_SEC * 10;
		conf.vibro = CNT_MSEC * 20;
		conf.vibro_detect = CNT_MSEC * 200;
		conf.vibro_aft = CNT_MSEC * 600;
		conf.lcd_power = PWM_LCD_CNT;
		conf.vibro_power = PWM_VIBRO_CNT;
	}
}

static void write_conf(void){
	conf.crc8 = crc8((uint8_t*)&conf, sizeof(conf) - 1);
	eeprom_write_block((void*)&conf, (void*)0, sizeof(conf));
}

static uint8_t crc8(uint8_t *buf, uint8_t size){
	uint8_t crc = 0xe6, tmp, bit_cnt, feedback_bit;

	while(size--){
		tmp = *buf++;
		for(bit_cnt = 8; bit_cnt; bit_cnt--){
			feedback_bit = (crc & 0x01);
			crc>>= 1;
			if(feedback_bit ^ (tmp & 0x01))
				crc^= 0x8c;
			tmp>>= 1;
		}
	}

	return crc;
}
